﻿namespace AlgoHero.Modelo
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text;
    using System.Xml;
    using AlgoHero.Vista;

    public class Cancion
    {
        #region Atributos

        private string nombre;        
        private double tiempoDeCompas;
        private Partitura partitura;
        private MapeoDeTeclas mapeo;
        private MapaTemporal mapaTemporal;

        #endregion Atributos

        #region Constructores

        public Cancion(string nombre, Partitura partitura, double tiempoDeCompas, MapeoDeTeclas mapeo)
        {
            /* Precondiciones: tiempoDeCompas debe ser un número entero positivo mayor a 0.
             * Postcondición: lanza ArgumentException si tiempoDeCompas es inválido.
             */
            if (tiempoDeCompas <= 0)
            {
                throw new ArgumentException("El tiempo de compás debe ser positivo.");
            }

            this.nombre = nombre;
            this.partitura = partitura;
            this.tiempoDeCompas = tiempoDeCompas;
            this.mapeo = mapeo;
            this.CalcularMapaTemporal();                        
        }

        public Cancion(XmlDocument documento)
        {
            try
            {
                XmlNodeList listaRaiz = documento.GetElementsByTagName("Cancion");
                XmlAttributeCollection atributosRaiz = listaRaiz[0].Attributes;

                string nombreCancion = atributosRaiz.GetNamedItem("nombre").Value;
                string sTiempoDeCompas = atributosRaiz.GetNamedItem("tiempodecompas").Value;
                double tiempoDeCompas = Convert.ToDouble(sTiempoDeCompas);

                this.nombre = nombreCancion;
                this.tiempoDeCompas = tiempoDeCompas;

                XmlNodeList listaNodoPartitura = documento.GetElementsByTagName("Partitura");
                XmlElement nodoPartitura = (XmlElement)listaNodoPartitura[0];
                this.partitura = new Partitura(nodoPartitura);

                XmlNodeList listaNodoMapeoDeTeclas = documento.GetElementsByTagName("MapeoDeTeclas");
                XmlElement nodoMapeoDeTeclas = (XmlElement)listaNodoMapeoDeTeclas[0];
                this.mapeo = new MapeoDeTeclas(nodoMapeoDeTeclas);

                this.CalcularMapaTemporal();
            }
            catch (XmlException ex)
            {
                throw new ArchivoInvalidoException("El archivo es invalido.");
            }
        }


        #endregion

        #region Propiedades

        public string Nombre
        {
            get { return nombre; }
        }
        
        public Partitura Partitura
        {
            get { return partitura; }
        }
        
        public double TiempoDeCompas
        {
            get { return tiempoDeCompas; }
        }
        
        public MapeoDeTeclas Mapeo
        {
            get { return mapeo; }
            set { mapeo = value; }
        }

        public MapaTemporal MapaTemporal
        {
            get { return mapaTemporal; }
        }

        public double Duracion  //Obtiene la duracion total de la cancion, en segundos.
        {
            get { return this.mapaTemporal.TiempoTotal; }
        }

        #endregion

        #region Métodos

        public int CantidadDeTeclas()
        {
            return this.mapeo.CalcularTeclasDiferentes().Count;
        }

        public IList<Tecla> TeclasHabilitadas()
        {
            return this.mapeo.CalcularTeclasDiferentes();
        }

        private void CalcularMapaTemporal()
        {
            this.mapaTemporal = new MapaTemporal(this);
        }

        public IList<Tecla> ObtenerTeclasAPresionar(double tiempoDeReproduccion)
        {
            /* Obtiene las teclas a presionar para un instante de reproduccion de la cancion.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            IList<Tecla> teclasAPresionar = this.mapaTemporal.ObtenerTeclasAPresionar(tiempoDeReproduccion);

            return teclasAPresionar;
        }

        public IList<Tecla> ObtenerTeclasAPresionar(double tiempoDeReproduccion, double amplitud)
        {
            /* Sobrecarga. Obtiene las teclas a presionar para un intervalo de la forma
             * tiempoDeReproduccion + amplitud. Devuelve null si no hay teclas.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             *                 La amplitud debe ser no negativo.
             * Postcondiciones: Lanza ArgumentException si tiempo o amplitud es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */
            IList<Tecla> teclasAPresionar;
            try
            {
                teclasAPresionar = this.mapaTemporal.ObtenerTeclasAPresionar(tiempoDeReproduccion, amplitud);
            }
            catch (TiempoInvalidoException e)
            {
                throw new TiempoInvalidoException(e.Message);
            }

            return teclasAPresionar;
        }

        public Acorde ObtenerAcordePara(double tiempoDeReproduccion)
        {
            /* Devuelve el acorde correspondiente al tiempo de reproduccion dado.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            Acorde acorde = this.mapaTemporal.ObtenerAcordePara(tiempoDeReproduccion);

            return acorde;
        }

        public double ObtenerTiempoDeProxAcorde(double tiempoDeReproduccion)
        {
            return this.mapaTemporal.ObtenerTiempoDeProxAcorde(tiempoDeReproduccion);
        }

        public bool EsValida(IList<Tecla> teclasPresionadas, double tiempoDeReproduccion)
        {
            /* Devuelve TRUE si la combinacion de teclas es valida para el instante tiempoDeReproduccion.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            IList<Tecla> teclasCorrectas = this.ObtenerTeclasAPresionar(tiempoDeReproduccion);

            foreach (Tecla tecla in teclasPresionadas)
            {
                if (!teclasCorrectas.Contains(tecla))// esto no contempla si al usuario le faltaron teclas por presionar
                {
                    return false;
                }
            }

            return true;
        }

        public int DameCantidadDeTeclasPerdidas(double tiempoDeReproduccion)
        {
            int cantidadTeclasPerdidas = this.mapaTemporal.DameCantidadDeTeclasPerdidas(tiempoDeReproduccion);

            return cantidadTeclasPerdidas;
        }


        public bool EsValida(Tecla teclaPresionada, double tiempoDeReproduccion)
        {
           /* Devuelve TRUE si la teclas es valida para el instante tiempoDeReproduccion.
            * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
            * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
            * si tiempoDeReproduccion es mayor que la duracion de la cancion.
            */
            return (this.ObtenerTeclasAPresionar(tiempoDeReproduccion).Contains(teclaPresionada));
        }

        public bool EsValida(Tecla teclaPresionada, double tiempoDeReproduccion, double amplitud)
        {
            IList<Tecla> teclasAPresionar = this.ObtenerTeclasAPresionar(tiempoDeReproduccion, amplitud);

            return teclasAPresionar.Contains(teclaPresionada);
        }

        //internal IList<NotaMidi> ConvertirAcordeAMidi(Acorde unAcorde)
        //{
        //    IList<NotaMidi> listaMidi = new List<NotaMidi>();
        //    NotaMidi unMidi;
        //    foreach (Nota nota in unAcorde)
        //    {
        //        unMidi = new NotaMidi();
        //        unMidi.Duracion = unAcorde.CalcularDuracionFisica(this.tiempoDeCompas, this.Partitura.DuracionDeCompas);
        //        unMidi.NumeroMidi = (int)nota.Frecuencia;
        //        listaMidi.Add(unMidi);
        //    }
        //    return listaMidi;
        //}

        public void Serializar()
        {
            XmlDocument documento = new XmlDocument();

            XmlNode nodoRaiz = documento.CreateNode(XmlNodeType.Element, "Cancion", "");

            XmlAttribute attNombre = documento.CreateAttribute("nombre");
            attNombre.Value = this.nombre;

            XmlAttribute attTiempoDeCompas = documento.CreateAttribute("tiempodecompas");
            attTiempoDeCompas.Value = this.tiempoDeCompas.ToString();

            nodoRaiz.Attributes.Append(attNombre);
            nodoRaiz.Attributes.Append(attTiempoDeCompas);            

            this.partitura.SerializarEn(documento, nodoRaiz);
            this.mapeo.SerializarEn(documento, nodoRaiz);

            documento.AppendChild(nodoRaiz);

            XmlTextWriter textWriter = new XmlTextWriter(this.nombre + ".xml", null);
            documento.Save(textWriter);
        }

               

        #endregion



    }
}
